<template>
  <v-row>
    <v-col cols="9">
      <v-container fluid>
        <v-list-item>
          <v-list-item-content>
            <v-list-item-title>
              <span v-text="title" />
              <span>学习状态图</span>
            </v-list-item-title>
          </v-list-item-content>
          <div>
            <v-btn
              color="primary"
              class="mr-2"
              v-if="!teacher"
              @click="setMyStatus()"
              >我的学习状态</v-btn
            >
            <v-btn @click="setAllStatus()">查看整体学习状态</v-btn>
          </div>
        </v-list-item>
        <v-list-item id="d3" align="center" justify="center" />
      </v-container>
    </v-col>
    <v-col cols="3">
      <v-row>
        <v-card scrollable outlined height="100%" width="100%">
          <v-list>
            <v-subheader>学生列表</v-subheader>
            <v-list-item-group color="primary">
              <v-list-item
                v-for="(stu, index) in students"
                :key="stu.name"
                link
                @click="setStuStatus(index)"
              >
                <v-list-item-title v-text="stu.name" />
              </v-list-item>
            </v-list-item-group>
          </v-list>
        </v-card>
      </v-row>
    </v-col>
  </v-row>
</template>
<script>
import * as d3 from "d3";
import { mapState, mapGetters } from "vuex";

export default {
  computed: {
    ...mapState(["students", "teacher"]),
    ...mapGetters({
      levels: "levels"
    }),
    delta() {
      return Math.PI / this.total;
    },
    timeInterval() {
      // 更新外圈数据的时间间隔
      // 与内圈高亮周期一致
      return (this.maxProgress * this.interval) / this.myLevels.length;
    }
  },
  data: () => ({
    timer: null,
    timer2: null,
    interval: 5000,
    title: "整体",
    progresses: [
      4,
      6,
      8,
      5,
      5,
      7,
      9,
      10,
      11,
      13,
      15,
      12,
      4,
      6,
      7,
      3,
      5,
      7,
      8,
      9,
      1,
      3
    ],
    maxProgress: 16,
    progress: 0,
    questions: [
      {
        title: "NP 问题是什么意思？",
        progress: 3
      },
      {
        title: "如何做产品调查？",
        progress: 7
      },
      {
        title: "微积分的原理是什么？",
        progress: 10
      }
    ],
    progressBucket: [],
    _circles: [],
    splits: 8,
    _scaleBucket: null,
    _status: null,
    total: 200,
    radius: 200,
    wid: 30,
    myLevels: [
      0.2,
      0.2,
      0.2,
      0.3,
      0.2,
      0.3,
      0.2,
      0.3,
      0.3,
      0.5,
      0.5,
      0.3,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    showMyProgress: false,
    index: 1
  }),
  mounted() {
    // this.generateArc();
    this.generateGraph();
    this.timer = setInterval(this.updateProgress, this.interval);
    this.timer2 = setInterval(this.updateLevels, this.timeInterval);
  },
  beforeDestroy() {
    clearInterval(this.timer);
    clearInterval(this.timer2);
  },
  methods: {
    setAllStatus() {
      const data = this.interpolateData(this.levels, this.total);
      this.showMyProgress = false;
      this.title = "整体";
      this.updateStatus(data);
      this.clearPersonalProgress();
    },
    setMyStatus() {
      const data = this.interpolateData(this.myLevels, this.total);
      this.showMyProgress = true;
      this.title = "我的";
      this.updateStatus(data);
      this.updatePersonalProgress(this.progress);
    },
    setStuStatus(d) {
      const data = this.interpolateData(this.students[d].levels, this.total);
      this.showMyProgress = false;
      this.title = this.students[d].name;
      this.updateStatus(data);
      this.updatePersonalProgress(this.students[d].progress);
    },
    updateStatus(data) {
      const n = data.length,
        arc = d3
          .arc()
          .outerRadius(this.radius)
          .innerRadius(this.radius - this.wid)
          .startAngle((d, i) => ((2 * Math.PI) / n) * i - this.delta)
          .endAngle((d, i) => ((2 * Math.PI) / n) * (i + 1) + this.delta);
      this._status
        .data(data)
        .enter()
        .append("path")
        .attr("d", arc)
        .attr("class", "prog-arc")
        .attr("fill", d => this.color(d))
        .exit()
        .remove();
    },
    clearPersonalProgress() {
      this._circles.attr("fill", "blue");
    },
    updatePersonalProgress(progress) {
      const hl = this._scaleBucket(progress);
      this._circles.attr("fill", (d, i) => (i == hl ? "red" : "blue"));
    },
    updateProgress() {
      this.progress += 1;
      if (this.progress == this.maxProgress) {
        this.progress = 0;
      }
      if (this.showMyProgress) {
        this.updatePersonalProgress(this.progress);
      }
    },
    updateLevels() {
      if (this.myLevels[this.index] + 0.1 <= 1) {
        this.myLevels[this.index] = this.myLevels[this.index] + 0.1;
      }
      this.index += 1;
      if (this.index == this.myLevels.length) {
        this.index = 0;
      }
      if (this.showMyProgress) {
        this.setMyStatus();
      }
    },
    interpolateData(data, n) {
      // 将数据 data 插值扩充到大于 n 的规模
      while (data.length < n) {
        const newdata = [];
        for (let i = 0; i < data.length - 1; i++) {
          newdata.push(data[i]);
          newdata.push((data[i] + data[i + 1]) / 2);
        }
        newdata.push(data[data.length - 1]);
        data = newdata;
      }
      return data;
    },
    color(t) {
      // t 取值 [-1, 1]
      // [-1, 0) 为绿色，表示简单
      // (0, 1) 为红色，表示有难度
      if (t >= 0) {
        return d3.interpolate("white", "red")(t);
      } else {
        return d3.interpolate("white", "green")(-t);
      }
    },
    generateGraph() {
      const w = 600,
        h = 550,
        xCenter = w / 2,
        yCenter = h / 2,
        radius = 200,
        wid = 30,
        total = 200,
        delta = Math.PI / this.total,
        splits = this.splits;
      const svg = d3
        .select("#d3")
        .append("svg")
        .attr("width", w)
        .attr("height", h);

      // 画分界线
      const g0 = svg.append("g");
      g0.attr("transform", "translate(" + xCenter + "," + yCenter + ")");
      const lines = g0
        .selectAll(".axis")
        .data(d3.range(-90, 270, 360 / splits))
        .enter()
        .append("g")
        .attr("transform", d => "rotate(" + d + ")")
        .attr("class", "axis");
      lines
        .append("line")
        .attr("x2", radius)
        .attr("stroke", "black");

      // 画外圈
      const data = this.interpolateData(this.levels, total),
        n = data.length;
      const arc = d3
        .arc()
        .outerRadius(radius)
        .innerRadius(radius - wid)
        .startAngle((d, i) => ((2 * Math.PI) / n) * i - delta)
        .endAngle((d, i) => ((2 * Math.PI) / n) * (i + 1) + delta);
      const g1 = svg.append("g");
      this._status = g1.selectAll(".prog-arc");
      this._status = g1
        .selectAll(".prog-arc")
        .data(data)
        .enter()
        .append("path")
        .attr("d", arc)
        .attr("class", "prog-arc")
        .attr("fill", d => this.color(d))
        .exit()
        .remove();
      g1.attr("transform", "translate(" + xCenter + "," + yCenter + ")");
      const g2 = svg.append("g");
      g2.attr("transform", "translate(" + xCenter + "," + yCenter + ")");

      // 画内圈
      const piece = this.maxProgress / splits,
        thresholds = d3.range(1, splits).map(i => piece * i),
        values = d3.range(splits),
        maxDotSize = 30,
        progressBucket = d3.range(splits).map(() => 0),
        scaleBucket = d3
          .scaleThreshold()
          .domain(thresholds)
          .range(values),
        scaleSize = d3
          .scaleLinear()
          .domain([0, this.progresses.length])
          .range([0, maxDotSize]);
      this._scaleBucket = scaleBucket;
      const progresses = this.progresses.sort();
      progresses.map(i => {
        progressBucket[scaleBucket(i)] += 1;
        return i;
      });
      progressBucket.p = this.progress;
      this.progressBucket = progressBucket;
      const arc2 = d3
          .arc()
          .outerRadius(radius - wid)
          .innerRadius(radius - 3 * wid)
          .startAngle((d, i) => ((2 * Math.PI) / splits) * i)
          .endAngle((d, i) => ((2 * Math.PI) / splits) * (i + 1)),
        donuts = g2
          .selectAll(".donut")
          .data(this.progressBucket)
          .enter()
          .append("g")
          .attr("class", "donut");
      donuts
        .append("path")
        .attr("d", arc2)
        .attr("fill", "white")
        .attr("stroke-width", 1)
        .attr("stroke", "black")
        .attr("class", "donut-path")
        .on("mouseenter", function(d, i) {
          d3.select(this)
            .transition()
            .duration(200)
            .attr("fill", "yellow")
            .attr("opacity", 0.5)
            .attr("class", "donut-path-hl");
          const pos = arc2.centroid(d, i);
          const dx = 35;
          pos[0] += pos[0] > 0 ? -dx : dx;
          pos[1] += pos[1] > 0 ? -dx : dx;
          g2.append("text")
            .attr("class", "d-data")
            .attr("fill", "black")
            .attr("transform", "translate(" + pos + ")")
            .text(d);
        })
        .on("mouseout", function() {
          d3.selectAll(".d-data").remove();
          d3.selectAll(".donut-path-hl")
            .attr("fill", "white")
            .attr("class", "donut-path");
        });
      this._circles = donuts
        .append("circle")
        .attr("r", d => scaleSize(d))
        .attr("transform", (d, i) => "translate(" + arc2.centroid(d, i) + ")")
        .attr("fill", "blue")
        .attr("class", "progress-circle");

      // 提问的线条
      const scaleDeg = (2 * Math.PI) / this.maxProgress;
      const g3 = svg
          .append("g")
          .attr("class", "questions")
          .attr("transform", "translate(" + xCenter + "," + yCenter + ")"),
        q = g3
          .selectAll(".question")
          .data(this.questions)
          .enter()
          .append("g")
          .attr("class", "question")
          .attr(
            "transform",
            d => "rotate(" + ((360 * d.progress) / this.maxProgress - 90) + ")"
          );
      q.append("line")
        .attr("x1", radius - wid)
        .attr("x2", radius + wid)
        .attr("stroke", "blue")
        .attr("stroke-width", 3)
        .attr("opacity", 0.75)
        .on("mouseenter", function(d) {
          d3.select(this)
            .transition()
            .duration(200)
            .attr("opacity", 1)
            .attr("stroke-width", 5)
            .attr("x2", radius + 1.5 * wid);
          const x = (radius + 3 * wid) * Math.sin(scaleDeg * d.progress),
            y = -1 * (radius + 3 * wid) * Math.cos(scaleDeg * d.progress);
          g3.append("text")
            .attr("class", "q-title")
            .attr("fill", "black")
            .attr("x", x)
            .attr("y", y)
            .attr("dx", x > 0 ? -80 : 10)
            .attr("dy", y > 0 ? 10 : -10)
            .text(() => d.title);
        })
        .on("mouseout", function() {
          d3.select(this)
            .transition()
            .duration(200)
            .attr("opacity", 0.75)
            .attr("stroke-width", 3)
            .attr("x2", radius + wid);
          d3.selectAll(".q-title").remove();
        })
        .on("click", () => {
          this.$router.push({
            name: "问答讨论"
          });
        });
    }
  }
};
</script>
